NOTE
1.7 Redis Lua
Redis Lua usage, debugging, data-type conversion, supported libraries, logging, execution time, EVAL/EVALSHA, IDEs, and example scripts.
This is a historical learning note and may contain outdated or incomplete understanding.
1. Lua Introduction
2. Using Redis Lua
2.1. Execute a Lua Script
redis-cli --eval test4.lua shop_point shop_id_list , 1 1600482998753
Here shop_point and shop_id_list are KEYS, while 1 and 1600482998753 are ARGV. KEYS and ARGV are separated by ,.
2.2. Debug a Lua Script
- By default, a new session is started, meaning the server is not blocked; the script-debugging session is rolled back after completion.
redis-cli --ldb --eval test4.lua shop_point shop_id_list , 1 1600482998753
- If synchronous mode is needed, add
--ldb-sync-mode. In this mode, the Redis server cannot be accessed.
2.2.1. Common Commands
quit: terminate the session.restart: restart the debugging session from the beginning and reload the new version of the script from the file.help: help list.- In the session:
redis.debug()is equivalent to watch, whileprintoutputs variables to the console.b line-numbersets a breakpoint,b -line-numberremoves a breakpoint, andb 0removes all breakpoints.- Dynamic breakpoint:
if counter > 10 then redis.breakpoint() end.
- Dynamic breakpoint:
2.3. Execute Redis Commands in Lua
redis.callvs.redis.pcallThe difference is that if a Redis command fails,redis.callthrows the error outward for the caller to handle, whileredis.pcallonly returns the error to the Lua script for handling.
2.4. Redis and Lua Data-Type Conversion
Rule: Redis type -> Lua type -> Redis type. If the initial value and final value are the same, it can be converted.
2.4.1. Redis -> Lua
Redis integer reply -> Lua number
Redis bulk reply -> Lua string
Redis multi bulk reply -> Lua table (may have other Redis data types nested)
Redis status reply -> Lua table with a single ok field containing the status
Redis error reply -> Lua table with a single err field containing the error
Redis Nil bulk reply and Nil multi bulk reply -> Lua false boolean type
2.4.2. Lua -> Redis
Lua number -> Redis integer reply (the number is converted into an integer)
Lua string -> Redis bulk reply
Lua table (array) -> Redis multi bulk reply (truncated to the first nil inside the Lua array if any)
Lua table with a single ok field -> Redis status reply
Lua table with a single err field -> Redis error reply
Lua boolean false -> Redis Nil bulk reply.
2.5. Libraries Supported by Redis Lua
baselib.tablelib.stringlib.mathlib.structlib.cjsonlib.cmsgpacklib.bitoplib.redis.sha1hexfunction.redis.breakpointandredis.debugfunction in the context of the Redis Lua debugger.
2.6. Redis Lua Logging
- Configure the log path and log level in
redis.conf.
# 2. Specify the server verbosity level.
# 3. This can be one of:
# 4. debug (a lot of information, useful for development/testing)
# 5. verbose (many rarely useful info, but not a mess like the debug level)
# 6. notice (moderately verbose, what you want in production probably)
# 7. warning (only very important / critical messages are logged)
loglevel notice
# 8. Specify the log file name. Also 'stdout' can be used to force
# 9. Redis to log on the standard output.
logfile "redis.log"
- Use
redis.log(loglevel,message)to print logs.- Log levels:
redis.LOG_DEBUG redis.LOG_VERBOSE redis.LOG_NOTICE redis.LOG_WARNINGmessage:string.formatcan be used for formatted output.
2.7. Redis Lua Execution Time
- Lua scripts generally finish at the millisecond level. The maximum execution time defaults to 5 seconds and can be configured with
lua-time-limitinredis.conf. - After 5 seconds, Redis does not terminate the Lua script; it only records it in the log.
2.8. EVAL vs. EVALSHA
Because Redis does not guarantee persistence or replication of Lua-script cache, Redis may still clear the Lua-script cache in some scenarios, such as instance migration or configuration changes. This requires the client to be able to handle this error. Lua Script Usage Guidelines
3. Redis Lua IDE
3.1. ZeroBrane
ZeroBrane Studio - Lua IDE/editor/debugger for Windows, Mac OSX, and Linux
4. Redis Lua Examples
4.1. Recording Points
- Use a string to record the point list.
local shopPointKey = KEYS[1]
local shopIdListKey = KEYS[2]
local currentShopId = ARGV[1]
local ts = ARGV[2]
-- checkSetCurrentPoint
local function checkCurrentPoint()
local shopJson = redis.call("HGET", shopPointKey, currentShopId)
redis.log(redis.LOG_NOTICE, "checkCurrentPoint: get point for shopPointKey=", shopPointKey, ", currentShopId=", currentShopId, "result=", shopJson)
if shopJson then
local shop = cjson.decode(shopJson)
if shop.start_time ~= 0 then
return 10001
end
end
return 0
end
-- checkSetPrevPoint
local function checkSetPrevPoint()
local newShopList = {}
local shopIdList = {}
local preShopIndex = 0
local shopIdListjson = redis.call("GET", shopIdListKey)
redis.log(redis.LOG_NOTICE, "checkSetPrevPoint: get shop id list for shopIdListKey=", shopIdListKey, "result=", shopIdListjson)
if shopIdListjson then
shopIdList = cjson.decode(shopIdListjson)
preShopIndex = table.getn(shopIdList)
for i, v in pairs(shopIdList) do
if v ~= currentShopId then
table.insert(newShopList, v)
end
end
end
table.insert(newShopList, currentShopId)
if preShopIndex ~= 0 then
local preShopId = shopIdList[preShopIndex]
local preShopJson = redis.call("HGET", shopPointKey, preShopId)
redis.log(redis.LOG_NOTICE, "checkSetPrevPoint: get pre shop for shopPointKey=", shopPointKey, "preShopId=", preShopId, "result=", preShopJson)
if preShopJson then
local preShop = cjson.decode(preShopJson)
if (preShop.end_time == nil) or (preShop.end_time == 0) then
preShop.end_time = ts
local preShopJsonNew = cjson.encode(preShop)
redis.call("HSET", shopPointKey, preShopId, preShopJsonNew)
redis.log(redis.LOG_NOTICE, "checkSetPrevPoint: set pre shop end time for shopPointKey=", shopPointKey, "preShopId=", preShopId, "result=", preShopJsonNew)
end
end
end
return newShopList, 0
end
-- setCurrentPoint
local function setCurrentPoint()
local currentShopPoint = {
start_time = ts
}
local currentShopPointJson = cjson.encode(currentShopPoint)
redis.call("HSET", shopPointKey, currentShopId, currentShopPointJson)
redis.log(redis.LOG_NOTICE, "setCurrentPoint: set current shop start time for shopPointKey=", shopPointKey, "currentShopId=", currentShopId, "result=", currentShopPointJson)
return 0
end
-- setShopList
local function setShopList(newShopList)
local newShopListJson = cjson.encode(newShopList)
redis.call("SET", shopIdListKey, newShopListJson)
redis.log(redis.LOG_NOTICE, "setShopList: update shop list for shopIdListKey=", shopIdListKey, "newShopListJson=", newShopListJson)
end
local function main()
redis.log(redis.LOG_NOTICE, "set point: request. keys=", shopPointKey, shopIdListKey, ", argv=", currentShopId, ts)
--local ret = checkCurrentPoint()
--if ret ~= 0 then
-- redis.log(redis.LOG_WARNING, "set point: checkCurrentPoint error. ret=", ret)
-- return ret
--end
local newShopList, ret = checkSetPrevPoint()
if ret ~= 0 then
redis.log(redis.LOG_WARNING, "set point: checkCurrcheckSetPrevPointentPoint error. ret=", ret)
return ret
end
ret = setCurrentPoint()
if ret ~= 0 then
redis.log(redis.LOG_WARNING, "set point: setCurrentPoint error. ret=", ret)
return ret
end
setShopList(newShopList)
return 0
end
return main()
- Use a list to record the point list.
local shopPointKey = KEYS[1]
local shopIdListKey = KEYS[2]
local currentShopId = ARGV[1]
local ts = ARGV[2]
-- checkSetCurrentPoint
local function checkCurrentPoint()
local shopJson = redis.call("HGET", shopPointKey, currentShopId)
redis.log(redis.LOG_NOTICE,
string.format("checkCurrentPoint: get current point. keys=%s,%s, result=%s",
shopPointKey, currentShopId, shopJson))
if shopJson then
local shop = cjson.decode(shopJson)
if shop.start_time ~= 0 then
return 10001
end
end
return 0
end
-- checkSetPrevPoint
local function checkSetPrevPoint()
local preShopId = redis.call("LINDEX", shopIdListKey, -1)
redis.log(redis.LOG_NOTICE, string.format("checkSetPrevPoint: get pre shop id. key=%s, result=%s",
shopIdListKey, preShopId))
if preShopId then
local preShopJson = redis.call("HGET", shopPointKey, preShopId)
redis.log(redis.LOG_NOTICE, string.format("checkSetPrevPoint: get pre shop. key=%s,%s, result=%s",
shopPointKey, preShopId, preShopJson))
if preShopJson then
local preShop = cjson.decode(preShopJson)
if (preShop.end_time == nil) or (preShop.end_time == 0) then
preShop.end_time = ts
local preShopJsonNew = cjson.encode(preShop)
redis.call("HSET", shopPointKey, preShopId, preShopJsonNew)
redis.log(redis.LOG_NOTICE,
string.format("checkSetPrevPoint: set pre shop end time. key=%s,%s, result=%s",
shopPointKey, preShopId, preShopJsonNew))
end
end
end
return 0
end
-- setCurrentPoint
local function setCurrentPoint()
local currentShopPoint = {
start_time = ts
}
local currentShopPointJson = cjson.encode(currentShopPoint)
redis.call("HSET", shopPointKey, currentShopId, currentShopPointJson)
redis.log(redis.LOG_NOTICE,
string.format("setCurrentPoint: set current shop start time. key=%s,%s, result=%s",
shopPointKey, currentShopId, currentShopPointJson))
return 0
end
-- updateShopList
local function updateShopList()
redis.call("LREM", shopIdListKey, 1, currentShopId)
redis.call("RPUSH", shopIdListKey, currentShopId)
redis.log(redis.LOG_NOTICE, string.format("updateShopList: update shop list. key=%s,%s",
shopIdListKey, currentShopId))
end
local function main()
redis.log(redis.LOG_NOTICE, string.format("set point: request params: keys=%s, %s, argv=%s, %s",
shopPointKey, shopIdListKey, currentShopId, ts))
--local ret = checkCurrentPoint()
--if ret ~= 0 then
-- redis.log(redis.LOG_WARNING,
-- string.format("set point: checkCurrentPoint error: ret=%d", ret))
-- return ret
--end
local ret = checkSetPrevPoint()
if ret ~= 0 then
redis.log(redis.LOG_WARNING,
string.format("set point: checkCurrcheckSetPrevPointentPoint error: ret=%d", ret))
return ret
end
ret = setCurrentPoint()
if ret ~= 0 then
redis.log(redis.LOG_WARNING,
string.format("set point: setCurrentPoint error: ret=%d", ret))
return ret
end
updateShopList()
return 0
end
return main()
4.2. Add a Key
local wantToSeeKeysKey = KEYS[1]
local wantToSeeKey = KEYS[2]
-- existsKey
local function existsKey()
local existsFlag = false
local res = redis.call("LRANGE", wantToSeeKeysKey, 0, -1)
redis.log(redis.LOG_NOTICE, "existsKey: wantToSeeKeysKey=", wantToSeeKeysKey, "res=", res)
if res then
for i, v in pairs(res) do
if v == wantToSeeKey then
existsFlag = true
break
end
end
end
return existsFlag
end
-- main
local function main()
redis.log(redis.LOG_NOTICE, "addWantToSeeKeys: req params: keys=", wantToSeeKeysKey, wantToSeeKey)
if existsKey() then
redis.log(redis.LOG_WARNING, "addWantToSeeKeys: key exitst")
return 10001
end
redis.call("RPUSH", wantToSeeKeysKey, wantToSeeKey)
redis.log(redis.LOG_NOTICE, "addWantToSeeKeys: add", wantToSeeKey, "to", wantToSeeKeysKey)
return 0
end
return main()
4.3. Initialize the Number of Seckill Participants
local activityKey = KEYS[1]
local goodsId = ARGV[1]
local participants = tonumber(ARGV[2])
local expireTs = tonumber(ARGV[3])
local function isActivityKeyExists()
return redis.call("EXISTS", activityKey)
end
local function setActivityKeyExpireTs()
return redis.call("EXPIRE", activityKey, expireTs)
end
local function incrGoodsParticipant()
return redis.call("HINCRBY", activityKey, goodsId, participants)
end
local function getGoodsParticipant()
return redis.call("HGET", activityKey, goodsId)
end
local function main()
if isActivityKeyExists() == 0 then
local goodsParticipants = incrGoodsParticipant()
setActivityKeyExpireTs()
return goodsParticipants
end
local goodsParticipants = getGoodsParticipant()
if goodsParticipants and goodsParticipants ~= 0 then
return goodsParticipants
else
return incrGoodsParticipant()
end
end
return main()
5. QA
5.1. CROSSSLOT Keys in request don’t hash to the same slot
6. References
- Write Redis Lua Script with ZeroBrane Studio | Blackie’s Failed Notes
- Redis Lua scripts debugger – Redis
- Develop and debug Redis Lua scripts with ZeroBrane Studio - YouTube
- Redis中lua脚本的调试 - zlAdmin - 博客园
- A Speed Guide To Redis Lua Scripting - Compose Articles
- lua-脚本尝试创建全局变量-代码日志
- Lua 脚本 — Redis 设计与实现
- EVAL – Redis
- Redis Lua scripts debugger – Redis
- zeroBrane 调试lua脚本 选择redis解释器才debug弹出url和输入密码password_百物易用是苏生-CSDN博客
- Lua脚本使用规范
Discussion
Sign in with GitHub to comment. Discussions are stored as GitHub Issues.View on GitHub